#include <iostream>
#include <fstream>
#include <string>
#include <initializer_list>

#include <glew.h>
#include <glut.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include "Shader.h"

Shader::Shader(const std::string & filename)
{
	std::string ext;
	char temp[10] = { 0 };
	int i = filename.length() - 1;
	int j = 0;

	this->filename = filename;

	while (filename[i] != '.' && i >= 0)
	{
		temp[j++] = filename[i];
		i--;
	}

	for (; j > 0; j--)
	{
		ext.append(&temp[j - 1], 1);
	}

	std::ifstream file{ filename };
	if (!file.is_open())
	{
		std::cerr << "cannot open file " << filename << ".\n";
		return;
	}

	i = 0;
	char * content = new char[1024];
	while (!file.eof())
	{
		content[i++] = file.get();
	}
	content[i - 1] = '\0';

	if (ext == "vert")
	{
		type = VERTEX_SHADER;
		ID = glCreateShader(GL_VERTEX_SHADER);
		glShaderSource(ID, 1, &content, NULL);
		std::cout << "compiling vertex shader from \"" << filename << "\"...\n";
		glCompileShader(ID);
	}
	else if (ext == "frag")
	{
		type = FRAGMENT_SHADER;
		ID = glCreateShader(GL_FRAGMENT_SHADER);
		glShaderSource(ID, 1, &content, NULL);
		std::cout << "compiling fragment shader from \"" << filename << "\"...\n";
		glCompileShader(ID);
	}

	int succeed;
	char info[1024] = { 0 };
	glGetShaderiv(ID, GL_COMPILE_STATUS, &succeed);
	if (!succeed)
	{
		glGetShaderInfoLog(ID, 1024, NULL, info);
		std::cerr << "cannot compile shader from \"" << filename << "\". ERROR: " << info << std::endl;
		return;
	}

	std::cout << "successfully compiled shader from \"" << filename << "\".\n";

	delete[] content;
	file.close();
}

Shader::Shader(const std::string & filename, int type)
{
	int i = 0;

	this->type = type;
	this->filename = filename;

	std::ifstream file{ filename };
	if (!file.is_open())
	{
		std::cerr << "cannot open file " << filename << ".\n";
		return;
	}

	char * content = new char[1024];
	while (!file.eof())
	{
		content[i++] = file.get();
	}
	content[i - 1] = '\0';

	if (type == VERTEX_SHADER)
	{
		ID = glCreateShader(GL_VERTEX_SHADER);
		glShaderSource(ID, 1, &content, NULL);
		std::cout << "compiling vertex shader from \"" << filename << "\"...\n";
		glCompileShader(ID);
	}
	else if (type == FRAGMENT_SHADER)
	{
		ID = glCreateShader(GL_FRAGMENT_SHADER);
		glShaderSource(ID, 1, &content, NULL);
		std::cout << "compiling fragment shader from \"" << filename << "\"...\n";
		glCompileShader(ID);
	}

	int succeed;
	char info[1024] = { 0 };
	glGetShaderiv(ID, GL_COMPILE_STATUS, &succeed);
	if (!succeed)
	{
		glGetShaderInfoLog(ID, 1024, NULL, info);
		std::cerr << "cannot compile shader from \"" << filename << "\". ERROR: " << info << std::endl;
		return;
	}

	std::cout << "successfully compiled shader from \"" << filename << "\".\n";

	delete[] content;
	file.close();
}

Shader::~Shader()
{
	glDeleteShader(ID);
}

int Shader::getShaderType()
{
	return type;
}

Program::Program()
{
	ID = glCreateProgram();
}

Program::~Program()
{
}

void Program::attach(const Shader & shader)
{
	glAttachShader(ID, shader.ID);
	std::cout << "shader \"" << shader.filename << "\" is attached to the program" << ID << std::endl;
}

void Program::attach(Shader * shader)
{
	glAttachShader(ID, shader->ID);
	std::cout << "shader \"" << shader->filename << "\" is attached to the program" << ID << std::endl;
}

bool Program::link()
{
	std::cout << "linking program...\n";
	glLinkProgram(ID);

	int succeed;
	glGetProgramiv(ID, GL_LINK_STATUS, &succeed);
	if (!succeed)
	{
		status = STATUS_FAIL_LINK;

		char info[1024] = { 0 };

		glGetProgramInfoLog(ID, 1024, NULL, info);
		std::cerr << "cannot link program. ERROR: " << info << std::endl;
		return false;
	}

	status = STATUS_LINKED_BUT_NOT_USED;

	std::cout << "successfully linked program" << ID << std::endl;

	return true;
}

void Program::use()
{
	//if (status == STATUS_LINKED_BUT_NOT_USED)
	//{
	//	glUseProgram(ID);
	//	status = STATUS_LINKED_AND_USED;
	//	//std::cout << "currently using program" << ID << std::endl;
	//}
	//else if(status == STATUS_UNLINKED)
	//{
	//	std::cerr << "ERROR: using unlinked program.\n";
	//}
	//else if (status == STATUS_FAIL_LINK)
	//{
	//	std::cerr << "ERROR: using bad-linked program.\n";
	//}

	glUseProgram(ID);
}

void Program::unuse()
{
	if (status == STATUS_LINKED_AND_USED)
	{
		glUseProgram(0);
		status = STATUS_LINKED_BUT_NOT_USED;
		std::cout << "program " << ID << " is unused.\n";
	}
}

void Program::uniform1i(const std::string & name, int value)
{
	int loc = glGetUniformLocation(ID, name.c_str());
	if (loc == -1)
	{
		std::cerr << "cannot locate uniform \"" << name << "\".\n";
		return;
	}
	glUniform1i(loc, value);

	std::cout << "in program " << ID << ": uniform \"" << name << "\" has been changed to " << value << ".\n";
}

void Program::uniform3f(const std::string & name, const glm::vec3 & value)
{
	int loc = glGetUniformLocation(ID, name.c_str());
	if (loc == -1)
	{
		std::cerr << "cannot locate uniform \"" << name << "\".\n";
		return;
	}
	//glUniform3f(loc, value.x, value.y, value.z);
	glUniform3fv(loc, 1, glm::value_ptr(value));
}

void Program::uniformmat4(const std::string & name, const glm::mat4 & value)
{
	int loc = glGetUniformLocation(ID, name.c_str());
	if (loc == -1)
	{
		std::cerr << "cannot locate uniform \"" << name << "\".\n";
		return;
	}
	glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(value));
}

void Program::uniform1f(const std::string & name, float value)
{
	int loc = glGetUniformLocation(ID, name.c_str());
	if (loc == -1)
	{
		std::cerr << "cannot locate uniform \"" << name << "\".\n";
		return;
	}
	glUniform1f(loc, value);
}

void Program::uniform1b(const std::string & name, bool value)
{
	int loc = glGetUniformLocation(ID, name.c_str());
	if (loc == -1)
	{
		std::cerr << "cannot locate uniform \"" << name << "\".\n";
		return;
	}
	glUniform1i(loc, static_cast<int>(value));
}

void Program::uniform2f(const std::string & name, const glm::vec2 & value)
{
	int loc = glGetUniformLocation(ID, name.c_str());
	if (loc == -1)
	{
		std::cerr << "cannot locate uniform \"" << name << "\".\n";
		return;
	}
	glUniform2fv(loc, 1, glm::value_ptr(value));
}

void Program::uniform2f(const std::string & name, float v1, float v2)
{
	int loc = glGetUniformLocation(ID, name.c_str());
	if (loc == -1)
	{
		std::cerr << "cannot locate uniform \"" << name << "\".\n";
		return;
	}
	glUniform2f(loc, v1, v2);
}